package com.onlyxiahui.framework.json.validator;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONReader;
import com.onlyxiahui.framework.json.validator.bean.ValidatorInfo;
import com.onlyxiahui.framework.json.validator.exception.ConfigError;
import com.onlyxiahui.framework.json.validator.exception.ValidatorConfigException;
import com.onlyxiahui.framework.json.validator.impl.*;
import com.onlyxiahui.framework.json.validator.impl.JsonUrlValidator;
import com.onlyxiahui.framework.json.validator.util.ValidatorJsonUtil;

/**
 * 
 * date 2018-12-18 09:00:19<br>
 * description
 * 
 * @author XiaHui<br>
 * @since 1.0.0
 */
public class ValidatorService {

	Map<String, JSONObject> map = new ConcurrentHashMap<String, JSONObject>();
	Map<String, Validator<JSONObject, Object>> validatorMap = new ConcurrentHashMap<String, Validator<JSONObject, Object>>();
	String validatorsKey = "validators";
	String nodesKey = "nodes";
	String currentKey = "";

	String validatorKey = "validator";
	String messageKey = "message";
	String codeKey = "code";
	String valueKey = "value";
	String extendKey = "extend";

	private final ValidatorContext validatorContext = new ValidatorContext(this);

	public ValidatorService() {
		initValidator();
	}

	public ValidatorService(String resourcePath) {
		initValidator();
		loadValidatorConfig(resourcePath);
	}

	/**
	 * 
	 * Description 加载配置文件，如果之前加载过，默认覆盖 <br>
	 * Date 2019-11-29 10:26:45<br>
	 * 
	 * @param resourcePath
	 * @since 1.0.0
	 */
	public void loadValidatorConfig(String resourcePath) {
		loadValidatorConfig(resourcePath, true);
	}

	/**
	 * 
	 * Description 加载配置文件 <br>
	 * Date 2019-11-29 10:27:20<br>
	 * 
	 * @param resourcePath
	 * @param coverExists  true:如果已经存在，那么就覆盖原来配置； false：如果已经存在，那么就跳过原来配置；
	 * @since 1.0.0
	 */
	public void loadValidatorConfig(String resourcePath, boolean coverExists) {
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		Resource[] resources = null;
		try {
			resources = resolver.getResources(resourcePath);
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		if (resources != null && resources.length > 0) {
			for (Resource resource : resources) {
				InputStream input = null;
				try {
					input = resource.getInputStream();
				} catch (IOException e) {
					e.printStackTrace();
					throw new RuntimeException(e);
				}

				if (null != input) {
					BufferedReader in = new BufferedReader(new InputStreamReader(input));
					JSONReader reader = new JSONReader(in);
					try {
						List<JSONObject> list = new ArrayList<>(1000);
						reader.startArray();
						while (reader.hasNext()) {
							Object o = reader.readObject();
							if (o instanceof JSONObject) {
								JSONObject jo = (JSONObject) o;
								list.add(jo);
							}
						}
						reader.endArray();
						for (JSONObject jo : list) {
							addValidatorConfig(jo, coverExists);
						}
					} finally {
						try {
							if (null != input) {
								input.close();
							}
						} catch (IOException e) {
							e.printStackTrace();
						}
						try {
							if (null != in) {
								in.close();
							}
						} catch (IOException e) {
							e.printStackTrace();
						}
						if (null != reader) {
							reader.close();
						}
					}
				}
			}
		}
	}

	/**
	 * 
	 * Description 添加配置文件，如果原来已经存在配置，默认合并配置 <br>
	 * Date 2019-11-29 10:28:32<br>
	 * 
	 * @param config
	 * @since 1.0.0
	 */
	public void addValidatorConfig(String config) {
		addValidatorConfig(config, false);
	}

	/**
	 * 
	 * Description 添加配置文件 <br>
	 * Date 2019-11-29 10:29:59<br>
	 * 
	 * @param config
	 * @param coverExists true:如果已经存在，那么就覆盖原来配置； false：如果已经存在，那么就跳过原来配置；
	 * @since 1.0.0
	 */
	public void addValidatorConfig(String config, boolean coverExists) {
		if (ValidatorJsonUtil.maybeJsonObject(config)) {
			JSONObject jo = JSONObject.parseObject(config);
			addValidatorConfig(jo, coverExists);
		} else if (ValidatorJsonUtil.maybeJsonArray(config)) {
			JSONArray array = JSONObject.parseArray(config);
			addValidatorConfig(array, coverExists);
		}
	}

	/**
	 * 
	 * Description 添加配置文件 <br>
	 * Date 2019-11-29 10:35:09<br>
	 * 
	 * @param array
	 * @param coverExists true:如果已经存在，那么就覆盖原来配置； false：如果已经存在，那么就跳过原来配置；
	 * @since 1.0.0
	 */
	private void addValidatorConfig(JSONArray array, boolean coverExists) {
		if (null != array && !array.isEmpty()) {
			int size = array.size();
			for (int i = 0; i < size; i++) {
				JSONObject jo = array.getJSONObject(i);
				addValidatorConfig(jo, coverExists);
			}
		}
	}

	/**
	 * 
	 * 添加验证配置Description <br>
	 * Date 2019-11-29 09:58:06<br>
	 * 
	 * @param jo
	 * @param coverExists true:如果已经存在，那么就覆盖原来配置； false：如果已经存在，那么就跳过原来配置；
	 * @since 1.0.0
	 */
	private void addValidatorConfig(JSONObject jo, boolean coverExists) {
		String name = jo.getString("name");

		if (name != null) {
			JSONObject validator = jo.getJSONObject("validator");
			List<ConfigError> list = check(name, validator);
			if (null != list && !list.isEmpty()) {
				throw new ValidatorConfigException(list);
			}

			if (coverExists) {
				map.put(name, validator);
			} else {
				JSONObject sourceValidatorJsonObject = map.get(name);
				if (null != sourceValidatorJsonObject) {
					merge(sourceValidatorJsonObject, validator);
				} else {
					map.put(name, validator);
				}
			}
		} else {
			List<ConfigError> list = new ArrayList<>();
			StringBuilder sb = new StringBuilder();
			sb.append("name配置不能为空！");
			ConfigError ce = new ConfigError();
			ce.setProperty(name);
			ce.setMessage(sb.toString());
			throw new ValidatorConfigException(list);
		}
	}

	public ValidatorContext getValidatorContext() {
		return validatorContext;
	}

	public void check() {
		for (String key : map.keySet()) {
			List<ConfigError> list = check(key, map.get(key));
			if (null != list && !list.isEmpty()) {
				throw new ValidatorConfigException(list);
			}
		}
	}

	public List<ConfigError> check(String name, JSONObject validatorJsonObject) {
		List<ConfigError> errorList = new ArrayList<>();
		if (null != validatorJsonObject) {
			Set<String> set = validatorJsonObject.keySet();
			for (String key : set) {
				if (validatorJsonObject.get(key) instanceof JSONObject) {
					JSONObject validatorInfo = validatorJsonObject.getJSONObject(key);
					check(key, validatorInfo, errorList);
				} else {
					StringBuilder sb = new StringBuilder();
					sb.append("配置有错误！");
					ConfigError ce = new ConfigError();
					ce.setProperty(key);
					ce.setMessage(sb.toString());
					errorList.add(ce);
				}
			}
		} else {
			StringBuilder sb = new StringBuilder();
			sb.append("name配置不能为空！");
			ConfigError ce = new ConfigError();
			ce.setProperty(name);
			ce.setMessage(sb.toString());
			errorList.add(ce);
		}
		return errorList;
	}

	public void check(String property, JSONObject validatorInfo, List<ConfigError> errorList) {

		boolean hasValidate = validatorInfo.containsKey(validatorsKey) && validatorInfo.get(validatorsKey) instanceof JSONArray;

		if (hasValidate) {
			JSONArray ja = validatorInfo.getJSONArray(validatorsKey);
			for (Object vo : ja) {
				if (vo instanceof JSONObject) {
					JSONObject v = (JSONObject) vo;
					String validatorName = v.getString(validatorKey);
					Validator<JSONObject, Object> validator = validatorMap.get(validatorName);
					if (null != validator) {
						Object value = v.get(valueKey);
						boolean mark = validator.check(value);
						if (!mark) {
							StringBuilder sb = new StringBuilder();
							sb.append("配置有错误！");
							ConfigError ce = new ConfigError();
							ce.setProperty(property);
							ce.setMessage(sb.toString());
							errorList.add(ce);
						}
					} else {
						StringBuilder sb = new StringBuilder();
						sb.append("[");
						sb.append(validatorName);
						sb.append("]");
						sb.append("验证类型不存在！");
						ConfigError ce = new ConfigError();
						ce.setProperty(property);
						ce.setMessage(sb.toString());
						errorList.add(ce);
					}
				} else {
					StringBuilder sb = new StringBuilder();
					sb.append("配置有错误！");
					ConfigError ce = new ConfigError();
					ce.setProperty(property);
					ce.setMessage(sb.toString());
					errorList.add(ce);
				}
			}
		}

		boolean hasNodes = validatorInfo.containsKey(nodesKey) && validatorInfo.get(nodesKey) instanceof JSONObject;
		if (hasNodes) {
			JSONObject nodeValidatorInfo = validatorInfo.getJSONObject(nodesKey);
			Set<String> set = nodeValidatorInfo.keySet();
			for (String key : set) {
				if (nodeValidatorInfo.get(key) instanceof JSONObject) {
					JSONObject vi = nodeValidatorInfo.getJSONObject(key);
					check(property + "." + key, vi, errorList);
				} else {
					StringBuilder sb = new StringBuilder();
					sb.append("配置有错误！");
					ConfigError ce = new ConfigError();
					ce.setProperty(property + "." + key);
					ce.setMessage(sb.toString());
					errorList.add(ce);
				}
			}
		}
	}

	public void merge(JSONObject sourceValidatorJsonObject, JSONObject mergeValidatorJsonObject) {
		if (null != sourceValidatorJsonObject && null != mergeValidatorJsonObject) {
			Set<String> set = mergeValidatorJsonObject.keySet();
			for (String key : set) {
				if (mergeValidatorJsonObject.get(key) instanceof JSONObject) {
					JSONObject mergeValidatorInfo = mergeValidatorJsonObject.getJSONObject(key);

					if (sourceValidatorJsonObject.get(key) instanceof JSONObject) {
						JSONObject sourceValidatorInfo = sourceValidatorJsonObject.getJSONObject(key);
						merge(key, sourceValidatorInfo, mergeValidatorInfo);
					} else {
						sourceValidatorJsonObject.put(key, mergeValidatorInfo);
					}
				}
			}
		}
	}

	public void merge(String property, JSONObject sourceValidatorInfo, JSONObject mergeValidatorInfo) {

		boolean hasSourceValidate = sourceValidatorInfo.containsKey(validatorsKey) && sourceValidatorInfo.get(validatorsKey) instanceof JSONArray;
		boolean hasMergeValidate = mergeValidatorInfo.containsKey(validatorsKey) && mergeValidatorInfo.get(validatorsKey) instanceof JSONArray;

		if (hasSourceValidate && hasMergeValidate) {
			JSONArray sourceArray = sourceValidatorInfo.getJSONArray(validatorsKey);
			JSONArray mergeArray = mergeValidatorInfo.getJSONArray(validatorsKey);
			sourceArray.addAll(mergeArray);
		} else if (hasMergeValidate) {
			JSONArray ja = sourceValidatorInfo.getJSONArray(validatorsKey);
			sourceValidatorInfo.put(validatorsKey, ja);
		}

		boolean hasSourceNodes = sourceValidatorInfo.containsKey(nodesKey) && sourceValidatorInfo.get(nodesKey) instanceof JSONObject;
		boolean hasMergeNodes = mergeValidatorInfo.containsKey(nodesKey) && mergeValidatorInfo.get(nodesKey) instanceof JSONObject;

		if (hasSourceNodes && hasMergeNodes) {
			JSONObject nodeSourceValidatorInfo = sourceValidatorInfo.getJSONObject(nodesKey);
			JSONObject nodeMergeValidatorInfo = mergeValidatorInfo.getJSONObject(nodesKey);
			Set<String> set = nodeMergeValidatorInfo.keySet();
			for (String key : set) {
				if (nodeMergeValidatorInfo.get(key) instanceof JSONObject) {
					JSONObject mergeValidator = nodeMergeValidatorInfo.getJSONObject(key);
					if (nodeSourceValidatorInfo.get(key) instanceof JSONObject) {
						JSONObject sourceValidator = nodeMergeValidatorInfo.getJSONObject(key);
						merge(key, sourceValidator, mergeValidator);
					} else {
						nodeSourceValidatorInfo.put(key, mergeValidator);
					}
				}
			}
		} else if (hasMergeNodes) {
			JSONObject nodeMergeValidatorInfo = mergeValidatorInfo.getJSONObject(nodesKey);
			sourceValidatorInfo.put(nodesKey, nodeMergeValidatorInfo);
		}
	}

	private void initValidator() {
		add(new JsonArrayValidator());
		add(new JsonBooleanValidator());
		add(new JsonEmailValidator());
		add(new JsonEqualsToValidator());
		add(new JsonInStringArrayValidator());
		add(new JsonIntegerValidator());
		add(new JsonMaxLengthValidator());
		add(new JsonMaxSizeValidator());
		add(new JsonMaxValidator());
		add(new JsonMinLengthValidator());
		add(new JsonMinSizeValidator());
		add(new JsonMinValidator());
		add(new JsonNotBlankValidator());
		add(new JsonNotEmptyValidator());
		add(new JsonNotNullValidator());
		add(new JsonNumberValidator());

		add(new JsonRegexValidator());
		add(new JsonUrlValidator());
	}

	public void add(Validator<JSONObject, Object> validator) {
		validatorMap.put(validator.getKey(), validator);
	}

	public boolean hasValidate(String validateName) {
		return map.containsKey(validateName);
	}

	public List<ValidatorResult> validate(String json, String validateName) {
		JSONObject jsonObject = JSONObject.parseObject(json);
		return validate(jsonObject, validateName);
	}

	public List<ValidatorResult> validate(String json, ValidatorInfo validatorInfo) {
		JSONObject jsonObject = JSONObject.parseObject(json);
		JSONObject validatorJsonObject = validatorInfo == null ? null : JSONObject.parseObject(JSONObject.toJSONString(validatorInfo));
		return validate(jsonObject, validatorJsonObject);
	}

	public List<ValidatorResult> validate(JSONObject jsonObject, String validateName) {
		JSONObject validatorJsonObject = map.get(validateName);
		return validate(jsonObject, validatorJsonObject);
	}

	private List<ValidatorResult> validate(JSONObject jsonObject, JSONObject validatorJsonObject) {
		List<ValidatorResult> list = new ArrayList<ValidatorResult>();
		if (null != validatorJsonObject) {
			Set<String> set = validatorJsonObject.keySet();
			for (String key : set) {
				if (validatorJsonObject.get(key) instanceof JSONObject) {
					JSONObject validatorInfo = validatorJsonObject.getJSONObject(key);
					if (currentKey.equals(key)) {
						validate(jsonObject, jsonObject, validatorInfo, list, key);
					} else {
						Object value = jsonObject.get(key);
						validate(jsonObject, value, validatorInfo, list, key);
					}
				}
			}
		}
		return list;
	}

	private void validate(JSONObject data, Object attribute, JSONObject validatorInfo, List<ValidatorResult> list, String property) {

		boolean hasValidate = validatorInfo.containsKey(validatorsKey) && validatorInfo.get(validatorsKey) instanceof JSONArray;

		if (hasValidate) {
			JSONArray ja = validatorInfo.getJSONArray(validatorsKey);
			for (Object vo : ja) {
				if (vo instanceof JSONObject) {
					JSONObject v = (JSONObject) vo;
					String validatorName = v.getString(validatorKey);
					Validator<JSONObject, Object> validator = validatorMap.get(validatorName);
					if (null != validator) {
						String code = v.getString(codeKey);
						String message = v.getString(messageKey);
						Object value = v.get(valueKey);
						Object extend = v.get(extendKey);

						ValidatorData<JSONObject, Object> vd = new ValidatorData<>();
						vd.setData(data);
						vd.setAttribute(attribute);
						vd.setCode(code);
						vd.setMessage(message);
						vd.setJudge(value);
						vd.setExtend(extend);

						ValidatorResult vr = validator.valid(validatorContext, vd);
						if (null != vr) {
							vr.setPropertyPath(property);
							list.add(vr);
						}
					}
				}
			}
		}

		boolean hasNodes = validatorInfo.containsKey(nodesKey) && validatorInfo.get(nodesKey) instanceof JSONObject;
		if (hasNodes && null != attribute) {
			JSONObject nodeValidatorInfo = validatorInfo.getJSONObject(nodesKey);

			if (attribute instanceof JSONArray) {
				arrayValidate(data, (JSONArray) attribute, nodeValidatorInfo, list, property);
			} else if (attribute instanceof JSONObject) {
				objectValidate(data, (JSONObject) attribute, nodeValidatorInfo, list, property);
			} else {
				objectValidate(data, null, nodeValidatorInfo, list, property);
			}

			// 不兼任老版本的写法{"nodes":{"validators":[]}} 新的写法{"nodes":{"":{"validators":[]}}}
//			boolean validate = nodeValidatorInfo.containsKey(validatorsKey) && nodeValidatorInfo.get(validatorsKey) instanceof JSONArray;
//
//			if (validate) {
//				if (attribute instanceof JSONArray) {
//					arrayOldValidate(data, (JSONArray) attribute, nodeValidatorInfo, list, property);
//				} else {
//					validate(data, attribute, nodeValidatorInfo, list, property);
//				}
//			} else {
//				
//			}
		}
	}

//	private void arrayOldValidate(JSONObject data, JSONArray array, JSONObject validatorInfo, List<ValidatorResult> list, String property) {
//		int size = array.size();
//		for (int i = 0; i < size; i++) {
//			Object value = array.get(i);
//			validate(data, value, validatorInfo, list, property + ".[" + i + "]");
//		}
//	}

	private void arrayValidate(JSONObject data, JSONArray array, JSONObject validatorInfo, List<ValidatorResult> list, String property) {
		if (null != array) {
			int size = array.size();
			if (size <= 0) {
				objectValidate(data, null, validatorInfo, list, property + ".[" + 0 + "]");
			} else {
				for (int i = 0; i < size; i++) {
					Object value = array.get(i);
					if (value instanceof JSONObject) {
						objectValidate(data, (JSONObject) value, validatorInfo, list, property + ".[" + i + "]");
					} else {
						Set<String> set = validatorInfo.keySet();
						for (String key : set) {
							if (validatorInfo.get(key) instanceof JSONObject) {
								JSONObject vi = validatorInfo.getJSONObject(key);
								validate(data, value, vi, list, property + ".[" + i + "]");
							}
						}
					}
				}
			}
		}
	}

	private void objectValidate(JSONObject data, JSONObject attribute, JSONObject validatorInfo, List<ValidatorResult> list, String property) {

		if (null != validatorInfo) {
			Set<String> set = validatorInfo.keySet();
			for (String key : set) {
				if (validatorInfo.get(key) instanceof JSONObject) {
					JSONObject vi = validatorInfo.getJSONObject(key);
					if (currentKey.equals(key)) {
						validate(data, attribute, vi, list, property);
					} else {
						Object value = (null == attribute) ? null : attribute.get(key);
						validate(data, value, vi, list, property + "." + key);
					}
				}
			}
		}
	}
}
